package com.eugenes.jvm;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class OnFlyClassLoader extends ClassLoader {
    
    private byte[] getBytes(File file) throws IOException {
        byte[] result = null;
        
        try (FileInputStream fis = new FileInputStream(file)) {
            long fileLength = file.length();

            if (fileLength > Integer.MAX_VALUE) {
                throw new IOException("File is too long.");
            }
            
            result = new byte[(int)fileLength];

            if (fis.read(result) != fileLength) {
                throw new IOException("Cannot read all bytes of file.");
            }
        }
        
        return result;
    }
    
    private boolean compile(String filename) {
        List<String> arguments = new ArrayList<>();
        arguments.add("javac");
        arguments.add(filename);
        
        ProcessBuilder pb = new ProcessBuilder(arguments);
        Process p = null;
        
        try {
            p = pb.start();
            p.waitFor();
        } catch (InterruptedException | IOException ex) {
            System.out.println(ex);
        }
        
        return (p != null && 0 == p.exitValue());
    }
    
    private void throwClassNotFoundException(String reason) throws ClassNotFoundException {
        throw new ClassNotFoundException(reason);
    }
    
    @Override
    public Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        Class<?> result = findLoadedClass(name);
        String path = name.replace('.', File.separatorChar);
        File javaFile = new File(path + ".java");
        File javaClassFile = new File(path + ".class");
        
        if (!javaFile.exists()) {
            String toAdd = "src" + File.separator;
            String checkFile = toAdd + javaFile.getPath();
            if (new File(checkFile).exists()) {
                javaFile = new File(toAdd + javaFile);
                javaClassFile = new File(toAdd + javaClassFile);
            }
        }
        
        if (javaFile.exists()
                && (!javaClassFile.exists() || javaClassFile.lastModified() < javaFile.lastModified())) {
            
            if (!compile(javaFile.getPath()) || !javaClassFile.exists()) {
                if (result == null) {
                    throwClassNotFoundException("Cannot compile file.");
                } else {
                    return result;
                }
            }
        }
        
        try {
            byte[] classBytes = getBytes(javaClassFile);
            
            if (classBytes != null && result == null) {
                result = defineClass(name, classBytes, 0, classBytes.length);
            }
        } catch (IOException ex) {
            // nothing to do here
        }
        
        if (result == null &&
                (result = findSystemClass(name)) == null) {
            throw new ClassNotFoundException();
        }
        
        if (resolve) {
            resolveClass(result);
        }
        
        return result;
    }
}
